/*******************************************************************************
 * Copyright © 2012 Ben Swim
 *
 * This file is part of ImoSPC.
 *
 * ImoSPC is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * ImoSPC is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * ImoSPC. If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package net.smwcentral;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;

import javax.swing.*;
import javax.swing.tree.*;

/**
 * Allows the user to select an SPC file from a Zip archive.
 * 
 * @author ShadowFan-X
 */
class FilePicker extends JDialog {
	// Look for the specified component's parent frame.
	private static Frame getFrameOfComponent(Component comp) {
		Container container = comp.getParent();
		while (!(container instanceof Frame))
			container = container.getParent();
		return (Frame)container;
	}
	
	// Interface for an object that has a tooltip.
	private static class NodeWithTooltip extends DefaultMutableTreeNode {
		private String m_tooltip;
		
		public NodeWithTooltip(Object v, boolean allowsChildren, String tt) {
			super(v, allowsChildren);
			m_tooltip = tt;
		}
		
		public final String getTooltip() { return m_tooltip; }
		public final void setTooltip(String tooltip) { m_tooltip = tooltip; }
	}
	
	// Renderer that supports tooltips.
	private static final class TooltipRenderer extends DefaultTreeCellRenderer {
		@Override
		public Component getTreeCellRendererComponent(JTree tree, Object value,
						boolean sel, boolean expanded, boolean leaf, int row,
						boolean hasFocus) {
			if (value instanceof NodeWithTooltip) {
				setToolTipText(((NodeWithTooltip)value).getTooltip());
			}
			return super.getTreeCellRendererComponent(tree, value, sel,
								expanded, leaf, row, hasFocus);
        }
    }

	// A specialized leaf node that has a byte array associated with it.
	private static final class LeafWithValue extends NodeWithTooltip {
		private String m_tooltip;
		public final int value;
		
		public LeafWithValue(String name, int value) {
			super(name, false, null);
			this.value = value;
		}
		
		public LeafWithValue(String name, int value, String tooltip) {
			super(name, false, tooltip);
			this.value = value;
		}
	}
	
	/** Look for a child node with the specified value. */
	private static DefaultMutableTreeNode findParentNode(MutableTreeNode parent,
														 Object value) {
		Enumeration e = parent.children();
		
		while (e.hasMoreElements()) {
			Object o = e.nextElement();
			
			if (o instanceof DefaultMutableTreeNode) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode)o;
				if (node.getAllowsChildren() &&
					value.equals(node.getUserObject()))
					return node;
			}
		}
		
		return null;
	}
	
	// Tries to get the name of an SPC; returns the file name otherwise.
	private static String getName(String name, byte[] data) {
		String title = DataHelper.getSpcTitle(data);
		if (title != null) {
			title = title.trim();
			if (!title.isEmpty()) return title;
		}
		return name;
	}
	
	// Builds a certain number of strings into a path.
	private static String makePath(String[] paths, int count) {
		StringBuilder sb = new StringBuilder();
		// Note: paths[0] is ""
		for (int i = 1; i < count; i++) {
			sb.append('/');
			sb.append(paths[i]);
		}
		return sb.toString();
	}
	// Same as above, but uses all path parts.
	private static String makePath(String[] paths) {
		return makePath(paths, paths.length);
	}
	
	// Expands all nodes in a tree.
	private static void expandTree(TreePath parent, JTree tree) {
		TreeNode node = (TreeNode)parent.getLastPathComponent();
		if (!node.isLeaf()) {
			for (Enumeration e = node.children(); e.hasMoreElements();) {
				TreeNode next = (TreeNode)e.nextElement();
				if (!next.isLeaf()) {
					TreePath nextPath = parent.pathByAddingChild(next);
					expandTree(nextPath, tree);
				}
			}
		}
		
		tree.expandPath(parent);
	}
	
	/** Creates new form FilePicker */
	private FilePicker(Component parent, FixedMap<String, byte[]> values,
						String rootTitle) {
		super(getFrameOfComponent(parent), true);
		initComponents();
		
		// Enable tooltips.
		m_tree.setCellRenderer(new TooltipRenderer());
		ToolTipManager.sharedInstance().registerComponent(m_tree);
		
		// Default root title
		if (rootTitle == null || rootTitle.isEmpty())
			rootTitle = "root";
		
		NodeWithTooltip root = new NodeWithTooltip(rootTitle, true, "/");
		DefaultTreeModel model = new DefaultTreeModel(root);
		
		// Get names
		String[] names = values.keySet().toArray(new String[0]);
		Arrays.sort(names, new Comparator<String>() {
			@Override
			public final int compare(String a, String b) {
				return a.compareTo(b);
			}
		});
		
		// Add all entries to the tree.
		for (int i = 0; i < names.length; i++) {
			String key = names[i];
			
			// Get the paths and the name of the file.
			String[] paths = key.split("[\\/]+");
			String name = getName(paths[paths.length - 1], values.get(key));
			// Create a leaf node with the name and data.
			LeafWithValue leaf = new LeafWithValue(name, values.getIndex(key),
													makePath(paths));
			// First path will be an empty string
			if (paths.length > 2) {
				DefaultMutableTreeNode node = root;
				
				// Find or create the leaf's parent.
				for (int j = 1; j < paths.length - 1; j++) {
					String dir = paths[j];
					DefaultMutableTreeNode newNode = findParentNode(node, dir);
					if (newNode == null) {
						newNode=new NodeWithTooltip(dir,true,makePath(paths,j+1));
						node.add(newNode);
					}
					node = newNode;
				}
				node.add(leaf);
			} else {
				root.add(leaf);
			}
		}
		
		m_tree.setModel(model);
		expandTree(new TreePath(root), m_tree);
		m_tree.setSelectionRow(0);
		
		// Look in a file called imoico for icons to use.
		final Class<?> cl = FilePicker.class;
		InputStream iconsLister = cl.getResourceAsStream("imoico");
		BufferedReader reader = null;
		
		try {
			reader = new BufferedReader(
							new InputStreamReader(iconsLister, "UTF-8"));
			ArrayList<Image> icons = new ArrayList<Image>();
			Toolkit tk = Toolkit.getDefaultToolkit();
			// Load all the icon names.
			for (String line; (line = reader.readLine()) != null;)
				if (!line.isEmpty())
					icons.add(tk.getImage(cl.getResource(line)));
			
			// Set the icons for this window.
			setIconImages(icons);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (reader != null)
					reader.close();
				else
					iconsLister.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		getRootPane().setDefaultButton(m_accept);

		getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "CANCEL");
		getRootPane().getActionMap().put("CANCEL", new AbstractAction(){
			@Override
			public void actionPerformed(ActionEvent e)
			{
				m_isAccept = false;
				setVisible(false);
			}
		});
	}

	public static NavigableMapIterator<String, byte[]> promptForSPCFileToOpen(
			Component parent, FixedMap<String, byte[]> values, String rootTitle)
			throws Exception {
		FilePicker picker = new FilePicker(parent, values, rootTitle);
		try {
			picker.setLocationRelativeTo(null);
			picker.setVisible(true);
			if (picker.m_isAccept) {
				Object selected =
							picker.m_tree.getSelectionPath().getLastPathComponent();
				// The selected item must be a LeafWithValue object.
				if (selected != null &&
					selected instanceof LeafWithValue) {
					return values.iterator(((LeafWithValue)selected).value);
				} else if (selected != null) {
					throw new Exception("The selected item is not an SPC file.");
				}
			}
		} finally {
			picker.dispose();
		}
		// No item was selected.
		return null;
	}
	
	/** This method is called from within the constructor to
	 * initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is
	 * always regenerated by the Form Editor.
	 */
	@SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        m_accept = new javax.swing.JButton();
        m_cancel = new javax.swing.JButton();
        javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
        m_tree = new javax.swing.JTree();
        javax.swing.JLabel jLabel1 = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setTitle("Play SPC File");
        setMinimumSize(new java.awt.Dimension(250, 275));

        m_accept.setMnemonic('P');
        m_accept.setText("Play");
        m_accept.setEnabled(false);
        m_accept.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                m_acceptActionPerformed(evt);
            }
        });

        m_cancel.setMnemonic('C');
        m_cancel.setText("Cancel");
        m_cancel.setDefaultCapable(false);
        m_cancel.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                m_cancelActionPerformed(evt);
            }
        });

        m_tree.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                m_treeMouseClicked(evt);
            }
        });
        m_tree.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
            public void valueChanged(javax.swing.event.TreeSelectionEvent evt) {
                m_treeValueChanged(evt);
            }
        });
        jScrollPane1.setViewportView(m_tree);

        jLabel1.setText("Select an SPC file to play.");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 280, Short.MAX_VALUE)
                    .addComponent(jLabel1)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(m_accept)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(m_cancel)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 254, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(m_cancel)
                    .addComponent(m_accept))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

	private void m_treeValueChanged(javax.swing.event.TreeSelectionEvent evt) {//GEN-FIRST:event_m_treeValueChanged
		// The selected item must be an instance of LeafWithValue to be used.
		m_accept.setEnabled(
			evt.getPath().getLastPathComponent() instanceof LeafWithValue);
	}//GEN-LAST:event_m_treeValueChanged

	private boolean m_isAccept;
	
	private void m_acceptActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_m_acceptActionPerformed
		m_isAccept = true;
		setVisible(false);
	}//GEN-LAST:event_m_acceptActionPerformed

	private void m_cancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_m_cancelActionPerformed
		m_isAccept = false;
		setVisible(false);
	}//GEN-LAST:event_m_cancelActionPerformed

	private void m_treeMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_m_treeMouseClicked
		// Double-clicking an item in the tree is the same as selecting it and
		// clicking Play.
		if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() == 2) {
			if (m_tree.getSelectionPath().getLastPathComponent()
					instanceof LeafWithValue) {
				m_isAccept = true;
				setVisible(false);
			}
		}
	}//GEN-LAST:event_m_treeMouseClicked
	
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton m_accept;
    private javax.swing.JButton m_cancel;
    private javax.swing.JTree m_tree;
    // End of variables declaration//GEN-END:variables
}
